<?php declare(strict_types=1);

namespace FallingTs\Utils;

use Closure;
use ReflectionObject;
use ReflectionException;

class AdvData
{
    /**
     * 静态数据转换
     *
     * @param array $src
     * @param array $rule
     * @param int|null $i
     * @return array
     * @throws ReflectionException
     */
    public static function trans(array $src, array $rule, ?int $i = null): array
    {
        $advData = new self;
        return $advData->transform($src, $rule, $i);
    }

    /**
     * 数据转换
     *
     * @param array $src
     * @param array $rule
     * @param int|null $i
     * @return array
     * @throws ReflectionException
     */
    public function transform(array $src, array $rule, ?int $i = null): array
    {
        $skips = $rule['_skips'] ?? null;
        $dest = [];
        if (is_array($skips)) {
            $dest = collect($src)->filter(function ($_, $key) use ($skips) {
                return !in_array($key, $skips);
            })->all();
        }

        if ($this->_isIndexedArray($src)) {
            foreach ($src as $i => $s) {
                $dest[$i] = $this->transform($s, $rule, $i);
            }

            return $dest;
        }

        foreach ($rule as $k => $r) {
            if ($k === '_skips') {
                continue;
            }

            if (is_int($k)) {
                $k = $r;
                if (str_contains($r, ' as ')) {
                    $r = explode(' as ', $r)[0];
                }
                if (str_contains($r, '||')) {
                    $k = explode('||', $r)[0];
                }
            }

            list($srcKey, $destKey) = [$k, $k];
            if (str_contains($k, ' as ')) {
                list($srcKey, $destKey) = explode(' as ', $k);
            }

            if (is_array($r)) {
                if ($this->_isIndexedArray($src[$srcKey])) {
                    foreach ($src[$srcKey] as $i => $s) {
                        $dest[$destKey][$i] = $this->transform($s, $r, $i);
                    }
                } else {
                    $dest[$destKey] = $this->transform($src[$srcKey], $r);
                }

            } else if ($r instanceof Closure) {
                $params = (new ReflectionObject($r))
                    ->getMethod('__invoke')
                    ->getParameters();
                if (!count($params)) {
                    $dest[$destKey] = $r();
                    continue;
                }
                $params = collect($params)->map(function ($param) use ($i, $src) {
                    $field = $param->name;
                    if ($field === '_src') {
                        return $src;
                    }
                    if ($field === '_index') {
                        return $i;
                    }
                    return $src[$field] ?? null;
                })->all();
                $dest[$destKey] = $r(...$params);
            } else {
                $dest[$destKey] = $this->_parseChains($src, explode('->', $r));
            }
        }
        return $dest;
    }

    /**
     * 获得链数据
     *
     * @param array $src
     * @param array $chains
     * @return mixed
     */
    protected function _parseChains(array $src, array $chains): mixed
    {
        $chain = array_shift($chains);
        $chainWithDefault = explode('||', $chain);
        $chain = $chainWithDefault[0];

        $tmp = $src[$chain] ?? null;
        if ($chains) {
            return $this->_parseChains($tmp, $chains);
        }

        if ($tmp === null) {
            $default = $chainWithDefault[1] ?? null;
            if ($default === '[]') {
                return [];
            }
            if ($default !== null) {
                return $default;
            }
            return $chain;
        }

        return $tmp;
    }

    /**
     * 判断是否是索引数组
     *
     * @param mixed $dest
     * @return bool
     */
    protected function _isIndexedArray(mixed $dest): bool
    {
        if (!is_array($dest)) {
            return false;
        }

        $ks = array_keys($dest);
        return $ks === array_keys($ks);
    }
}